#include "args.h"
#include "gen.h"

void wrong_parameter(const char* required, PyObject* obj) {
	char buf[1000];
	sprintf(buf, "Cannot cast object of type %s. Required: %s",
		obj->ob_type->tp_name, required); 
	PyErr_SetString(PyExc_TypeError, buf);
}	

static void missing_parameter(int num) {
	char buf[1000];
	sprintf(buf, "Too few parameters provided: %d", num);
	PyErr_SetString(PyExc_TypeError, buf);
}	

static void redundant_parameter(int prov, int req) {
	char buf[1000];
	sprintf(buf, "Too many parameters provided: %d (required %d)", prov, req);
	PyErr_SetString(PyExc_TypeError, buf);
}	

static PyObject* 
get_arg(PyObject* args, int* args_used, bool has_def, bool* error) {
	if(*args_used>=PyTuple_Size(args)) {
		if(!has_def) {
			missing_parameter(PyTuple_Size(args));
			*error = true;
        }
        return NULL;
	}
    PyObject* ret = PyTuple_GetItem(args, *args_used);
	(*args_used)++;
    return ret;
}    

void init_context(func_context* context) {
	context->linksnum = 0;
}

GEN get_GEN_arg(PyObject* args, int* args_used, bool has_def, GEN def, bool* error) {
    PyObject* arg = get_arg(args, args_used, has_def, error);
    if(*error) return NULL;
    if(arg==NULL) return def;
    GEN ret = gen_cast(arg);
	if(ret==NULL) {
		*error = true;
	}
	return ret;
}

long get_long_arg(PyObject* args, int* args_used, bool has_def, long def, bool* error) {
    PyObject* arg = get_arg(args, args_used, has_def, error);
    if(*error) return 0;
    if(arg==NULL) return def;
	if(!PyNumber_Check(arg)) {
		wrong_parameter("integer", arg);
		*error = true;
		return 0;
	}
	long ret = PyInt_AsLong(arg);
	if(PyErr_Occurred()) {
		*error = true;
		return 0;
	}
	return ret;
}

const char* get_char_arg(PyObject* args, int* args_used, bool has_def, const char* def, bool* error) {
    PyObject* arg = get_arg(args, args_used, has_def, error);
    if(*error) return 0;
    if(arg==NULL) return def;
	if(!PyString_Check(arg)) {
		wrong_parameter("string", arg);
		*error = true;
		return NULL;
	}
	char* ret = PyString_AsString(arg);
	return ret;
}

GEN get_string_arg(PyObject* args, int* args_used, bool has_def, const char* def, bool* error) {
    PyObject* arg = get_arg(args, args_used, has_def, error);
    if(*error) return 0;
    if(arg==NULL) {
		return strtoGENstr(def);
    }
	if(!PyString_Check(arg)) {
		wrong_parameter("string", arg);
		*error = true;
		return NULL;
	}
	GEN ret = strtoGENstr(PyString_AsString(arg));
	return ret;
}

PyObject* get_ref_arg(PyObject* args, int* args_used, bool has_def, PyObject* def, bool* error) {
    PyObject* arg = get_arg(args, args_used, has_def, error);
    if(*error) return 0;
    if(arg==NULL) {
		return def;
    }
	if(!PyList_Check(arg)) {
		wrong_parameter("list", arg);
		*error = true;
		return NULL;
	}
	return arg;
}

void copy_reference(PyObject* ref, GEN val) {
    PyObject* obj = (PyObject*) create_gen(val);
    if(obj!=NULL) {
        PyList_Append(ref, obj);
        Py_DECREF(obj);
    }
}

entree* get_ref_var_arg(func_context* context, PyObject* args, int* args_used, bool* error) {
	entree* ent = (entree*) malloc(sizeof(entree));
	ent->value = (void*) gen_0;
	ent->args = NULL;
	ent->name = NULL;
	ent->next = NULL;
	ent->code = NULL;
	ent->help = NULL;
	ent->valence = 1;
	context->links[context->linksnum].source = (GEN*) &(ent->value);
	context->linksnum++;
	return ent;
}

#define SIZE_EVAL_STRING (sizeof(func_context*)+sizeof(PyObject*)+5)
char* get_eval_arg(func_context* context, PyObject* args, int* args_used, bool long_expression, bool* error) {
    PyObject* arg = get_arg(args, args_used, false, error);
    if(*error) return 0;
    if(arg==NULL) {
		*error = true;
        char* retstr = (char*) malloc(2);
        strcpy(retstr, "0");
        return retstr;
	}	
	char* retstr = (char*) malloc(SIZE_EVAL_STRING);
	char* strptr = retstr;
	*strptr = foreignExprSwitch;
	strptr++;
	*strptr = (char) long_expression;
	strptr++;
	*((func_context**) strptr) = context;
	strptr += sizeof(func_context*);
	*((PyObject**) strptr) = arg;
	return retstr;
}

long get_var_arg(PyObject* args, int* args_used, bool has_def, long def, bool* error) {
    PyObject* arg = get_arg(args, args_used, has_def, error);
    if(*error) return -1;
    if(arg==NULL) return def;
	GEN ret = gen_cast(arg);
	if(ret==NULL) {
		*error = true;
		return -1;
	}
	if(typ(ret)!=t_POL) {
		wrong_parameter("variable", arg);
		*error = true;
		return -1;
	}	
	return varn(ret);
}

entree* get_var_entree_arg(PyObject* args, int* args_used, bool* error) {
	long var = get_var_arg(args, args_used, false, -1, error);
	if(*error) return NULL;
	entree *ep = varentries[var];
	if (ep==NULL) {
		*error = true;
		PyErr_SetString(pari_error, "this function uses a killed variable");
	}
	return ep;
}

void test_remaining_arg(PyObject* args, int* args_used, bool* error) {
	if(*args_used<PyTuple_Size(args)) {
		redundant_parameter(PyTuple_Size(args), *args_used);
		*error = true;
	}
}

long get_real_precision() {
	return ndec2prec(pari_real_prec);
}

long get_series_precision() {
	return pari_series_prec;
}

GEN callback_func(char* expr) {
//	printf("Command for PARI: %X, %X\n", ((int*)expr)[0], ((int*)expr)[1]);
	expr++;
	bool long_expression = (bool) *expr;	
	expr++;
	func_context* context = *((func_context**) expr);
	expr+= sizeof(func_context*);
	PyObject* func = *((PyObject**) expr);
//	printf("Unpacking %d parameters\n", context->linksnum);
	PyObject* args = PyTuple_New(context->linksnum);
//	printf("Created tuple %X\n", args);
	if(args==NULL) {
		pari_err(talker, "Error in callback");
		return NULL;
	}		
	for(int i = 0; i<context->linksnum; i++) {
		PyObject* arg = (PyObject*) create_gen(*(context->links[i].source));
		if(arg==NULL) {
			Py_DECREF(args);
			pari_err(talker, "Error in callback");
			return NULL;
		}
		if(PyTuple_SetItem(args, i, arg)) {
			Py_DECREF(args);
			pari_err(talker, "Error in callback");
			return NULL;
		}			
	}
	PyObject* res_obj = PyObject_Call(func, args, NULL);
	Py_DECREF(args);
    if(res_obj==Py_None) {
		Py_DECREF(res_obj);
		return NULL;
    }        
	if(res_obj==NULL) {
		pari_err(talker, "Error in callback (see below)");
		return NULL;
	}
//	if(!long_expression) {
		GEN res = gen_cast(res_obj);
		if(res==NULL) {
			Py_DECREF(res_obj);
			pari_err(user, "Error in callback (see below)");
			return NULL;
		}
		if(isclone(res)) { //We were getting errors in intnum without this
			res = gcopy(res);
		}
		Py_DECREF(res_obj);
		return res;
//	} else {
//		Py_DECREF(res_obj);
//		return NULL;
//	}
}
